use anyhow::Result;
use rolldown::{Bundler, BundlerOptions, InputItem, SourceMapType};
use std::path::PathBuf;

/// Simple rolldown-based bundler configuration
#[derive(Debug, Clone)]
pub struct RolldownBundlerConfig {
    /// Entry file to bundle
    pub entry_path: PathBuf,
    /// Base directory for resolution (defaults to entry file's directory)
    pub base_dir: Option<PathBuf>,
    /// Output file path (if None, returns code as String)
    pub output_path: Option<PathBuf>,
    /// Enable source maps
    pub source_maps: bool,
}

/// Result of bundling operation
#[derive(Debug)]
pub struct BundleResult {
    /// Bundled JavaScript code
    pub code: String,
    /// Output file path (if written to file)
    #[allow(dead_code)]
    pub output_path: Option<PathBuf>,
}

/// Rolldown-based bundler
pub struct RolldownBundler {
    config: RolldownBundlerConfig,
}

impl RolldownBundler {
    /// Create a new rolldown bundler
    pub fn new(config: RolldownBundlerConfig) -> Self {
        Self { config }
    }

    /// Bundle the entry file and its dependencies into a single JavaScript file
    pub async fn bundle(&self) -> Result<BundleResult> {
        let base_dir = if let Some(base_dir) = &self.config.base_dir {
            base_dir.clone()
        } else {
            self.config
                .entry_path
                .parent()
                .ok_or_else(|| anyhow::anyhow!("Entry path has no parent directory"))?
                .to_path_buf()
        };

        let entry_str = if let Some(base_dir) = &self.config.base_dir {
            self.config
                .entry_path
                .strip_prefix(base_dir)
                .unwrap_or(&self.config.entry_path)
                .to_str()
                .ok_or_else(|| anyhow::anyhow!("Entry path is not valid UTF-8"))?
        } else {
            self.config
                .entry_path
                .to_str()
                .ok_or_else(|| anyhow::anyhow!("Entry path is not valid UTF-8"))?
        };

        let bundler_options = BundlerOptions {
            input: Some(vec![InputItem {
                name: None,
                import: entry_str.to_string(),
            }]),
            cwd: Some(base_dir.clone()),
            sourcemap: if self.config.source_maps {
                Some(SourceMapType::File)
            } else {
                None
            },
            ..Default::default()
        };

        let mut bundler = Bundler::new(bundler_options)?;

        let _result = bundler
            .write()
            .await
            .map_err(|e| anyhow::anyhow!("Rolldown bundling failed: {:?}", e))?;

        let dist_dir = base_dir.join("dist");

        if !dist_dir.exists() {
            return Err(anyhow::anyhow!(
                "Rolldown dist directory not found: {}",
                dist_dir.display()
            ));
        }

        let entries = std::fs::read_dir(&dist_dir)
            .map_err(|e| anyhow::anyhow!("Failed to read dist directory: {}", e))?;

        let mut js_files: Vec<_> = entries
            .filter_map(|e| e.ok())
            .filter(|e| e.path().extension().is_some_and(|ext| ext == "js"))
            .collect();

        if js_files.is_empty() {
            return Err(anyhow::anyhow!(
                "No JavaScript files generated by rolldown in dist directory"
            ));
        }

        js_files.sort_by_key(|f| f.file_name());

        let entry_stem = self
            .config
            .entry_path
            .file_stem()
            .and_then(|s| s.to_str())
            .unwrap_or("entry");

        let main_file = js_files
            .iter()
            .find(|f| {
                f.file_name()
                    .to_str()
                    .is_some_and(|name| name.starts_with(entry_stem))
            })
            .unwrap_or(&js_files[0]);

        let bundled_code = std::fs::read_to_string(main_file.path())
            .map_err(|e| anyhow::anyhow!("Failed to read generated bundle: {}", e))?;

        if let Some(output_path) = &self.config.output_path {
            std::fs::write(output_path, &bundled_code)
                .map_err(|e| anyhow::anyhow!("Failed to write bundle to file: {}", e))?;
        }

        Ok(BundleResult {
            code: bundled_code,
            output_path: self.config.output_path.clone(),
        })
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_rolldown_bundler_creation() {
        let config = RolldownBundlerConfig {
            entry_path: "test.js".into(),
            base_dir: None,
            output_path: None,
            source_maps: false,
        };

        let bundler = RolldownBundler::new(config.clone());

        assert_eq!(bundler.config.entry_path, config.entry_path);
        assert_eq!(bundler.config.source_maps, config.source_maps);
    }
}
